<?php

namespace App\service\order;

use Exception;
use support\Log;
use AdaPay\AdaPay;
use support\Redis;
use think\facade\Db;
use PhpAmqpLib\Wire\AMQPTable;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use App\service\box\BatchBoxServices;
use crmeb\exceptions\AdminException;
use think\db\exception\{DataNotFoundException, DbException, ModelNotFoundException};

include_once app_path() . '/../' . 'extend/AdapaySdk/init.php';
include_once app_path() . '/../' . 'extend/checkstand/class/H5Cashier.php';

class BoxOrderServices
{
    public function __construct()
    {
        AdaPay::init(app_path() . '/../' . '/extend/AdapayDemo/config/config.json', "live", false);
    }

    /**
     * 购买盲盒的创建订单
     * @param int $uid 用户id
     * @param int $box_id 盲盒id
     * @param int $buy_count 购买数量
     * @param string $pay_type 支付类型
     * @param int $order_pk_id 订单主键id 重新付款的时候 传入
     * @return array
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function boxOrderCreate(int $uid, int $box_id, int $buy_count, string $pay_type, int $order_pk_id = 0)
    {
        $detection = $this->detectionCount($uid, $box_id, $buy_count);
        $redis_user_info = $detection['redis_user_info'];
        $redis_box_info = $detection['redis_box_info'];

        $prefix = env('REDIS_PREFIX');
        $ttl_seconds = env('REDIS_TTL_SECONDS');
        $key = $prefix . $box_id . '_ex_box_id_num';

        $random = uniqid();
        $orderCode = $this->orderId() . $random;
        $txn_time_out = time() + 300;
        $result = [];

        if ($order_pk_id) {
            $getPayment = Db::name('store_order')->where('id', $order_pk_id)->find();

            if (!$getPayment) {
                throw new AdminException('发起重新支付的订单号不存在！');
            }
//            if ($getPayment['status'] == 4) {
//                throw new AdminException('订单已超时！');
//            }
            if ($getPayment['status'] != 4 && strtotime($getPayment['txn_time_out']) <= time()) {
                throw new AdminException('订单已过付款有效期！');
            }
        }

        switch ($pay_type) {
            case 'sd_h5_checkstand':
                // 杉德支付
                $client = new \H5Cashier;
                // 参数
                $totalAmount = intval($redis_box_info['price'] * 100);
                $totalAmount = intval($buy_count * $totalAmount);
                $var = sprintf("%012d", $totalAmount);
                $client->body = array(
                    'orderCode' => $orderCode,
                    'totalAmount' => $var,
                    'subject' => '购买宝箱',
                    'body' => '用户购买宝箱消费',
                    'notifyUrl' => env('PAY_NOTIFY_URL') . '/api/boxOrderApi/payNotify',
                    'frontUrl' => env('PAY_NOTIFY_URL') . '/orderApi/targetHome',
                    'userId' => substr($redis_user_info['mobile'], -10),
//                    'txnTimeOut' => date('YmdHis', $txn_time_out)
//                    'payModeList' => "[qppay,qrpay,h5ibank,alipay,wechatpay,unionpay,wxjsbridge,wxjsapi,alijsbridge,alijsapi]"
                );
                // 生成表单
                $form = $client->form('orderCreate');
                $result = $form;
                break;
        }

        // 购买 使用 nft_list表 的 主键id 这个id  到异步的队列里面  再去分配
        $sqlData = [
            'order_id' => $orderCode, // 订单号
            'type' => 1, // 订单类型 0 购买藏品 1 购买盲盒 2 钱包充值
            'uid' => $uid, // 用户id
            'account_id' => $redis_user_info['account_id'], // 购买人链账户id
            'mobile' => $redis_user_info['mobile'], // 用户手机号
            'box_id' => $box_id, // 盲盒ID
            'box_name' => $redis_box_info['name'], // 盲盒名称

            'buy_count' => $buy_count, // 购买数量
            'total_price' => bcmul((string)$redis_box_info['price'], (string)$buy_count, 2), // 订单总价
            'pay_price' => bcmul((string)$redis_box_info['price'], (string)$buy_count, 2), // 实际支付金额

            'txn_time_out' => date('YmdHis', $txn_time_out),
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s')
        ];

        // 开始事务
        Db::startTrans();

        try {
            $order_auto_id = Db::name('store_order')->insertGetId($sqlData);

            if (!$order_auto_id) {
                throw new AdminException('订单创建失败！');
            }

            if (!$order_pk_id) {
                $batchBoxServices = new BatchBoxServices();
                $return = $batchBoxServices->issueOneBoxManyNew($box_id, $buy_count, $uid, $redis_user_info['mobile'], (int)$order_auto_id);
                if (!$return) {
                    throw new AdminException('网络错误请稍后重试！');
                }
            } else {
                Db::name('store_order')->where('id', $order_pk_id)->update([
                    'status' => 4 // 之前的订单 手动标记超时
                ]);
                $batch_box_ids = Db::name('batch_box')->where('order_pk_id', $order_pk_id)->column('id');

                if ($batch_box_ids) {
                    Db::name('batch_box')->where('id', 'in', $batch_box_ids)->update(['order_pk_id' => $order_auto_id, 'updated_at' => date('Y-m-d H:i:s')]);
                }
            }

            // 提交事务
            Db::commit();
        } catch (\Exception $e) {
            // 回滚事务
            Db::rollback();
            throw new AdminException($e->getMessage());
        }

        // 发送 订单查询 延时队列
        $this->sendSelectOrderMsg($order_auto_id . ':1', 120000); // 2分钟
        $this->sendSelectOrderMsg($order_auto_id . ':2', 290000); // 4分50秒
        $this->sendSelectOrderMsg($order_auto_id . ':3', 350000); // 5分50秒
        $this->sendSelectOrderMsg($order_auto_id . ':4', 900000); // 15分
        $this->sendSelectOrderMsg($order_auto_id . ':5', 1800000);// 30分
        $this->sendSelectOrderMsg($order_auto_id . ':6', 3600000);// 60分

        return [
            'form' => $result,
            'order_id' => $orderCode
        ];
    }

    /**
     * 购买盲盒的创建订单 压力测试
     * @param int $uid 用户id
     * @param int $box_id 盲盒id
     * @param int $buy_count 购买数量
     * @param string $pay_type 支付类型
     * @param int $order_pk_id 订单主键id 重新付款的时候 传入
     * @return array
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function boxOrderCreateTest(int $uid, int $box_id, int $buy_count, string $pay_type, int $order_pk_id = 0)
    {
        $detection = $this->detectionCount($uid, $box_id, $buy_count);
        $redis_user_info = $detection['redis_user_info'];
        $redis_box_info = $detection['redis_box_info'];

        $prefix = env('REDIS_PREFIX');
        $ttl_seconds = env('REDIS_TTL_SECONDS');
        $key = $prefix . $box_id . '_ex_box_id_num';

        $random = uniqid();
        $orderCode = $this->orderId() . $random;
        $txn_time_out = time() + 300;
        $result = [];

        if ($order_pk_id) {
            $getPayment = Db::name('store_order')->where('id', $order_pk_id)->find();

            if (!$getPayment) {
                throw new AdminException('发起重新支付的订单号不存在！');
            }
            if ($getPayment['status'] == 4) {
                throw new AdminException('订单已超时！');
            }
            if ($getPayment['status'] != 4 && strtotime($getPayment['txn_time_out']) <= time()) {
                throw new AdminException('订单已过付款有效期！');
            }
        }

        switch ($pay_type) {
            case 'sd_h5_checkstand':
                // 杉德支付
                $client = new \H5Cashier;
                // 参数
                $totalAmount = intval($redis_box_info['price'] * 100);
                $totalAmount = intval($buy_count * $totalAmount);
                $var = sprintf("%012d", $totalAmount);
                $client->body = array(
                    'orderCode' => $orderCode,
                    'totalAmount' => $var,
                    'subject' => '购买宝箱',
                    'body' => '用户购买宝箱消费',
                    'notifyUrl' => env('PAY_NOTIFY_URL') . '/api/boxOrderApi/payNotify',
                    'frontUrl' => env('PAY_NOTIFY_URL') . '/orderApi/targetHome',
                    'userId' => substr($redis_user_info['mobile'], -10),
//                    'txnTimeOut' => date('YmdHis', $txn_time_out)
//                    'payModeList' => "[qppay,qrpay,h5ibank,alipay,wechatpay,unionpay,wxjsbridge,wxjsapi,alijsbridge,alijsapi]"
                );
                // 生成表单
                $form = $client->form('orderCreate');
                $result = $form;
                break;
        }

        // 购买 使用 nft_list表 的 主键id 这个id  到异步的队列里面  再去分配
        $sqlData = [
            'order_id' => $orderCode, // 订单号
            'type' => 1, // 订单类型 0 购买藏品 1 购买盲盒 2 钱包充值
            'uid' => $uid, // 用户id
            'account_id' => $redis_user_info['account_id'], // 购买人链账户id
            'mobile' => $redis_user_info['mobile'], // 用户手机号
            'box_id' => $box_id, // 盲盒ID
            'box_name' => $redis_box_info['name'], // 盲盒名称

            'buy_count' => $buy_count, // 购买数量
            'total_price' => bcmul((string)$redis_box_info['price'], (string)$buy_count, 2), // 订单总价
            'pay_price' => bcmul((string)$redis_box_info['price'], (string)$buy_count, 2), // 实际支付金额

            'txn_time_out' => date('YmdHis', $txn_time_out),
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s')
        ];

        // 开始事务
        Db::startTrans();

        try {
            $order_auto_id = Db::name('store_order')->insertGetId($sqlData);

            if (!$order_auto_id) {
                throw new AdminException('订单创建失败！');
            }

            if (!$order_pk_id) {
                $batchBoxServices = new BatchBoxServices();
                $return = $batchBoxServices->issueOneBoxManyNew($box_id, $buy_count, $uid, $redis_user_info['mobile'], (int)$order_auto_id);
                if (!$return) {
                    throw new AdminException('网络错误请稍后重试！');
                }
            } else {
                Db::name('store_order')->where('id', $order_pk_id)->update([
                    'status' => 4 // 之前的订单 手动标记超时
                ]);
                $batch_box_ids = Db::name('batch_box')->where('order_pk_id', $order_pk_id)->column('id');

                if ($batch_box_ids) {
                    Db::name('batch_box')->where('id', 'in', $batch_box_ids)->update(['order_pk_id' => $order_auto_id, 'updated_at' => date('Y-m-d H:i:s')]);
                }
            }

            // 提交事务
            Db::commit();
        } catch (\Exception $e) {
            // 回滚事务
            Db::rollback();
            throw new AdminException($e->getMessage());
        }

        // 发送 订单查询 延时队列
        $this->sendSelectOrderMsg($order_auto_id . ':1', 120000); // 2分钟
        $this->sendSelectOrderMsg($order_auto_id . ':2', 290000); // 4分50秒
        $this->sendSelectOrderMsg($order_auto_id . ':3', 350000); // 5分50秒
        $this->sendSelectOrderMsg($order_auto_id . ':4', 900000); // 15分
        $this->sendSelectOrderMsg($order_auto_id . ':5', 1800000);// 30分
        $this->sendSelectOrderMsg($order_auto_id . ':6', 3600000);// 60分

        $this->sendBoxNotifyTestMsg($order_auto_id, 30000); // 模拟3分钟后支付成功通知回调

        return [
            'form' => $result,
            'order_id' => $orderCode
        ];
    }

    /**
     * 盲盒购买
     * 杉德收银台H5支付回调通知
     * @param array $result
     * @return bool
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function boxPayNotifyNew(array $result)
    {
        $pos = json_encode($result, JSON_UNESCAPED_UNICODE);
        Log::info('支付通知返回post:' . $pos);

        if (empty($pos)) {
            throw new AdminException('杉德支付返回信息有误');
        }

        if (!isset($result['data'])) {
            throw new AdminException('杉德支付返回信息有误');
        }

        $data = $result['data'];
        $sign = $result['sign'];

        $client = new \H5Cashier;
        $data = stripslashes($data); //支付数据

        // 验签
        $verifyFlag = $client->verify($data, $sign);
        if (!$verifyFlag) {
            throw new AdminException('签名失败');
        }
        if (!empty($data)) {
            $data = json_decode($data, true);
        }
        $trade_no = $data['body']['tradeNo']; // 拿到订单号

        $orderInfo = Db::name('store_order')->where('order_id', $trade_no)->find();

        if (!$orderInfo) {
            throw new AdminException("订单不存在！");
        }
        // 盲盒信息
        $redis_box_info = $this->getBoxInfo($orderInfo['box_id']);

        if ($orderInfo['status'] == 1 || $orderInfo['status'] == 2) {
            throw new AdminException("订单已支付！");
        }
        if ($orderInfo['status'] == 4) {
            throw new AdminException("订单已超时！");
        }
        // 用户信息
//        $redis_user_info = $this->getUserInfo($orderInfo['uid']);
//        if (empty($redis_user_info['account_id'])) {
//            //throw new AdminException('您暂未区块链账户，请去实名认证页面查看并获取！');
//        }

        $this->sendBoxOrderSuccess($orderInfo['id'], 3000);

        return true;
    }

    /**
     * 订单查询后的 回调成功通知
     * @param int $order_pk_id
     * @return bool
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function selectBoxPayNotify(int $order_pk_id)
    {
        $orderInfo = Db::name('store_order')->where('id', $order_pk_id)->find();
        if (!$orderInfo) {
            return true; // 订单不存在
        }
        // 盲盒信息
        $redis_box_info = $this->getBoxInfo($orderInfo->box_id);

        if ($orderInfo['status'] == 1 || $orderInfo['status'] == 2) {
            return true; // 订单已支付
        }
        if ($orderInfo['status'] == 4) {
            return true; // 订单已超时
        }
        // 用户信息
//        $redis_user_info = $this->getUserInfo($orderInfo['uid']);
//        if (empty($redis_user_info['account_id'])) {
//            //throw new AdminException('您暂未区块链账户，请去实名认证页面查看并获取！');
//        }

//        $prefix = env('REDIS_PREFIX');

        $this->sendBoxOrderSuccess($orderInfo['id'], 3000);

        # 盲盒更新后的信息存入redis 同步
//        $boxInfo = Db::name('box')->where('id',$orderInfo['box_id'])->find();
//        Redis::set($prefix . 'box_id_' . $orderInfo['box_id'], json_encode($boxInfo));

        return true;
    }

    /**
     * 盲盒购买
     * 杉德收银台H5支付回调通知 压力测试
     * @param string $trade_no 订单表的主键id
     * @return bool
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function boxPayNotifyTest(string $trade_no)
    {
        $orderInfo = Db::name('store_order')->where('id', $trade_no)->find();
        if (!$orderInfo) {
            return true; // 订单不存在
        }
        // 盲盒信息
        $redis_box_info = $this->getBoxInfo($orderInfo['box_id']);

        if ($orderInfo['status'] == 1 || $orderInfo['status'] == 2) {
            return true; // 订单已支付
        }
        if ($orderInfo['status'] == 4) {
            return true; // 订单已超时
        }
        // 用户信息
        $redis_user_info = $this->getUserInfo($orderInfo['uid']);
//        if (empty($redis_user_info['account_id'])) {
//            //throw new AdminException('您暂未区块链账户，请去实名认证页面查看并获取！');
//        }

        $this->sendBoxOrderSuccess($orderInfo['id'], 3000);

        return true;
    }

    /**
     * 检测 用户 针对 某个盲盒 单人 可购买数量 是否满足限制条件 和 是否达到开售时间
     * @param int $uid
     * @param int $box_id
     * @param int $buy_count
     * @return array
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function detectionCount(int $uid, int $box_id, int $buy_count)
    {
        // 用户信息
        $redis_user_info = $this->getUserInfo($uid);
//        if (empty($redis_user_info['account_id'])) {
//            //throw new AdminException('您暂未拥有区块链账户，请去实名认证页面查看并获取！');
//        }

        // 盲盒信息
        $redis_box_info = $this->getBoxInfo($box_id);

        if (strtotime($redis_box_info['issuer_date']) > time()) {
            throw new AdminException('此盲盒暂未到开售时间，请您耐心等待！');
        }

        $can_buy_num = $redis_box_info['can_buy_num']; // 单人 可购买数量

        # 强制索引

        $my_buy_count = Db::name('store_order')
                ->force('idx_type_uid_boxid_status')
                ->where('type', 1)
                ->where('uid', $uid)
                ->where('box_id', $box_id)
                ->where('status', 'in', '0,1,2,5') // 订单状态（0 支付中 1 已付款未到账 2 已付款已到账 3 已退款 4 订单超时 5 支付失败（或者待支付））
                ->sum('buy_count') ?? 0;

        $my_buy_count = intval($my_buy_count);

        if ($my_buy_count >= $can_buy_num) {
            throw new AdminException('此盲盒单人可下单数量是' . $can_buy_num . '个！');
        }

        $my_buy_count = $my_buy_count + $buy_count;

        if ($my_buy_count > $can_buy_num) {
            throw new AdminException('此盲盒单人可下单数量是' . $can_buy_num . '个！');
        }

        return [
            'redis_user_info' => $redis_user_info,
            'redis_box_info' => $redis_box_info
        ];
    }


    /**
     * 返回用户信息
     * @param int $uid
     * @return mixed
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function getUserInfo(int $uid)
    {
        $prefix = env('REDIS_PREFIX');

        $user_key = $prefix . 'user_id_' . $uid;
        $user_is_exists = Redis::exists($user_key);
        if (!$user_is_exists) {
            $userInfo = Db::name('user')->where('id', $uid)->find(); // 用户信息
            $userData = [
                'user_id' => $uid,
                'account_id' => $userInfo['account_id'],
                'mobile' => $userInfo['mobile']
            ];
            Redis::set($user_key, json_encode($userData));
            Redis::set($prefix . 'user_mobile_' . $userInfo['mobile'], json_encode($userData));
        }

        $redis_user = Redis::get($user_key);
        $redis_user_info = json_decode($redis_user, true);
        return $redis_user_info;
    }

    /**
     * 获取盲盒信息
     * @param int $box_id
     * @return mixed
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function getBoxInfo(int $box_id)
    {
        $prefix = env('REDIS_PREFIX');

        $box_key = $prefix . 'box_id_' . $box_id;
        $box_is_exists = Redis::exists($box_key);
        if (!$box_is_exists) {
            $boxInfo = Db::name('box')->where('id', $box_id)->find();
            Redis::set($box_key, json_encode($boxInfo));
        }

        $redis_box = Redis::get($box_key);
        $redis_box_info = json_decode($redis_box, true);
        return $redis_box_info;
    }

    /**
     * @return string
     */
    public function orderId()
    {
        return date('Ymd') . substr(implode(null, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
    }

    /**
     * 发送盲盒订单支付成功查询的延迟队列 3秒 = 3000  id 表示 订单id
     * @param $id
     * @param $ttl
     * @return bool
     * @throws Exception
     */
    public function sendBoxOrderSuccess($id, $ttl = 3000)
    {
        $v_host = env('RABBITMQ_VHOST');
        $exc_name = 'delay_exc_box_order_success';
        $routing_key = 'delay_box_order_success';
        $queue_name = 'delay_queue_box_order_success';

//        $ttl = 300000; // 300秒 5分钟

        $connection = new AMQPStreamConnection('localhost', 5672, env('RABBITMQ_USER'), env('RABBITMQ_PWD'), $v_host);

        $channel = $connection->channel();
        $channel->exchange_declare($exc_name, 'x-delayed-message', false, true, false);
        $args = new AMQPTable(['x-delayed-type' => 'direct']);
        $channel->queue_declare($queue_name, false, true, false, false, false, $args);
        $channel->queue_bind($queue_name, $exc_name, $routing_key);

        $data = $id;

        $arr = ['delivery_mode' => AMQPMEssage::DELIVERY_MODE_PERSISTENT, 'application_headers' => new AMQPTable(['x-delay' => $ttl])];
        $msg = new AMQPMessage($data, $arr);
        $channel->basic_publish($msg, $exc_name, $routing_key);

        $channel->close();
        $connection->close();
        return true;
    }

    /**
     * 发送订单查询的延迟队列 20秒 = 20000
     * @param $id
     * @param $ttl
     * @return bool
     * @throws Exception
     */
    public function sendSelectOrderMsg($id, $ttl = 300000)
    {
        $v_host = env('RABBITMQ_VHOST');
        $exc_name = 'delay_exc_select_order';
        $routing_key = 'delay_select_order';
        $queue_name = 'delay_queue_select_order';

//        $ttl = 300000; // 300秒 5分钟

        $connection = new AMQPStreamConnection('localhost', 5672, env('RABBITMQ_USER'), env('RABBITMQ_PWD'), $v_host);

        $channel = $connection->channel();
        $channel->exchange_declare($exc_name, 'x-delayed-message', false, true, false);
        $args = new AMQPTable(['x-delayed-type' => 'direct']);
        $channel->queue_declare($queue_name, false, true, false, false, false, $args);
        $channel->queue_bind($queue_name, $exc_name, $routing_key);

        $data = $id;

        $arr = ['delivery_mode' => AMQPMEssage::DELIVERY_MODE_PERSISTENT, 'application_headers' => new AMQPTable(['x-delay' => $ttl])];
        $msg = new AMQPMessage($data, $arr);
        $channel->basic_publish($msg, $exc_name, $routing_key);
        $channel->close();
        $connection->close();
        return true;
    }

    /**
     * 发送盲盒购买成功压测通知的延迟队列 时间 3秒 = 3000
     * @param $id
     * @param $ttl
     * @return bool
     * @throws Exception
     */
    public function sendBoxNotifyTestMsg($id, $ttl = 3000)
    {
        $v_host = env('RABBITMQ_VHOST');
        $exc_name = 'delay_exc_box_notify_test';
        $routing_key = 'delay_box_notify_test';
        $queue_name = 'delay_queue_box_notify_test';

//        $ttl = 300000; // 300秒 5分钟

        $connection = new AMQPStreamConnection('localhost', 5672, env('RABBITMQ_USER'), env('RABBITMQ_PWD'), $v_host);

        $channel = $connection->channel();
        $channel->exchange_declare($exc_name, 'x-delayed-message', false, true, false);
        $args = new AMQPTable(['x-delayed-type' => 'direct']);
        $channel->queue_declare($queue_name, false, true, false, false, false, $args);
        $channel->queue_bind($queue_name, $exc_name, $routing_key);

        $data = $id;

        $arr = ['delivery_mode' => AMQPMEssage::DELIVERY_MODE_PERSISTENT, 'application_headers' => new AMQPTable(['x-delay' => $ttl])];
        $msg = new AMQPMessage($data, $arr);
        $channel->basic_publish($msg, $exc_name, $routing_key);
        $channel->close();
        $connection->close();
        return true;
    }
}
